package damon.taskThread;

import damon.swing.WriteJPanel;
import damon.utils.DataToHexStrUtil;
import damon.utils.OpcUtil;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;


/**
 * 该类是socket发送接收信息的类。
 * 比较重要的一个方面是该类嵌套了很多try-catch块。
 * 其中比较关键的向服务端读取数据的循环里的try块，主要分成两类：
 * 一类是会使socket连接断开的异常，该类异常是由socket发出的io流异常，该异常通过while循环外的try块处理，
 * 可以达到在io断开时，socket跳出内层业务循环，在外层连接循环中，一直尝试连接，直至连接成功！
 * 其他类异常，都会在业务循环内处理，不会使socket断开，目前包括：sql异常、运行时异常、sleep方法异常
 *
 * @author 风蚕
 */
@Getter
@Setter
public class WriteThread extends Thread {
    boolean runIs;//握手协议是否完成
    WriteJPanel writeJPanel;
    Socket socket = null;

    public WriteThread() {
    }


    public WriteThread(WriteJPanel client, Socket socket) {
        this.runIs = true;
        this.writeJPanel = client;
        this.socket = socket;
        start();
    }

    @Override
    public void run() {
        OutputStream os;
        InputStream is;
        int dbNum = Integer.parseInt(this.writeJPanel.getDBField().getText());
        int writeLen = Integer.parseInt(this.writeJPanel.getWriteLenField().getText());
        int startByte = Integer.parseInt(this.writeJPanel.getOffSetField().getText());
        String writeType = this.writeJPanel.getTypeBox().getSelectedItem().toString();
        String writeData = this.writeJPanel.getWriteDataField().getText();
        try {
            os = socket.getOutputStream();
            is = socket.getInputStream();
//            return;
        } catch (IOException e) {
            e.printStackTrace();
            writeJPanel.append("连接断开");
            try {
                this.socket.close();
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
            writeJPanel.disconnect();
            return;
        }

        try {
            //握手
            OpcUtil.handshake(is,os);
            //打印时间
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
            writeJPanel.append("time:" + sdf.format(new Date()));
            //根据数据类型生成不同的写入16进制字符串
            String hexWriteData = "";
            if("chars".equals(writeType)){
                hexWriteData = DataToHexStrUtil.strToHex(writeData);
            }else if("number".equals(writeType) || "number unsigned".equals(writeType)){
                hexWriteData = DataToHexStrUtil.numToHex(Long.valueOf(writeData),writeLen);
            }else if("real|lReal".equals(writeType)){
                if(writeLen==4){
                    hexWriteData = DataToHexStrUtil.floatToHex(Float.valueOf(writeData));
                }
                if(writeLen==8){
                    hexWriteData = DataToHexStrUtil.doubleToHex(Double.valueOf(writeData));
                }
            }else{
                writeJPanel.append("未知类型:" + writeType);
                throw new Exception("未知类型！");
            }

            String dataBytes = getDataBytes(is, os, writeLen,dbNum,startByte,hexWriteData);
            System.out.println(dataBytes);
            String endFlag = dataBytes.substring(dataBytes.length()-2);
            if("FF".equals(endFlag)) {
                writeJPanel.append("["+writeData+"]写入成功！");
            }else {
                writeJPanel.append("["+writeData+"]写入失败！");
            }
            writeJPanel.append("");

        } catch (IOException | DecoderException e) {
            e.printStackTrace();
            writeJPanel.append("服务端连接断开->"+ e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
            writeJPanel.append("异常导致连接断开->" + e.getMessage());
        }

        try {
            if (os != null) {
                os.close();
            }
            if (is != null) {
                is.close();
            }
            if (socket != null) {
                socket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();

        } finally {
            writeJPanel.disconnect();
        }


    }

    /**
     * 获取plc返回的原始报文
     * @param is
     * @param os
     * @param readByteLen
     * @param dbNo
     * @param startByte
     * @param writeData
     * @return
     * @throws Exception
     */
    public String getDataBytes(InputStream is,OutputStream os,int readByteLen,int dbNo,int startByte,String writeData) throws Exception {
        // 根据配置参数，组装向plc请求的报文
        String sendHexMsg = createSendMsg(readByteLen, dbNo,startByte,writeData);
        // 向plc发送读取db块请求，并获取返回数据的字节码数组
        byte[] replyBytes = OpcUtil.onceSocketCommunication(is, os, Hex.decodeHex(sendHexMsg));
//        System.out.println(replyBytes.length);
        if(replyBytes.length < 22){
            throw new RuntimeException("plc返回数据异常，小于22个字节！16进制报文：" + Hex.encodeHexString(replyBytes).toUpperCase());
        }
        String receiveHexStr = Hex.encodeHexString(replyBytes).toUpperCase();
        return receiveHexStr;
    }


    /**
     * 组装向plc发的报文
     * @param writeSize
     * @param DBNumber
     * @param startByte
     * @param writeData
     * @return
     * @throws Exception
     */
    public String createSendMsg(int writeSize,int DBNumber,int startByte,String writeData) throws Exception {
        String writeMsgTotalLen = DataToHexStrUtil.numToHex(35 + writeSize,2);//写入报文总长度
        String writeOtherLen = DataToHexStrUtil.numToHex(4 + writeSize,2);//一种不明白什么意思的长度
        String writeLen = DataToHexStrUtil.numToHex(writeSize,2);//写入数据长度
        String dbNum = DataToHexStrUtil.numToHex(DBNumber,2);//db块编号
        String offset = DataToHexStrUtil.numToHex(startByte*8,3);//按位的写入偏移量
        String writeBitLen = DataToHexStrUtil.numToHex(writeSize*8,2);//写入长度按位
        String date = writeData;
        String allMsg = "0300"
                +writeMsgTotalLen
                +"02F08032010000"
                +"0005" //序列号，返回会带回，可以标识报文
                +"000E"
                +writeOtherLen
                +"0501120A10"
                +"02"//写入方式，byte
                +writeLen
                +dbNum
                +"84"//写入方式，db块
                +offset
                +"0004"//写入方式byte
                +writeBitLen
                +date;
        return allMsg;
    }

}
